import { ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Observable, of } from 'rxjs'
import { NzMessageService } from 'ng-zorro-antd'
import * as dayjs from 'dayjs'
import { STPage, STWidthMode, STComponent, STChange } from '@delon/abc'
import { SettingsService } from '@delon/theme'

import { I18NService } from '@core'
import { ISearchSchema } from '@shared/interfaces/search'
import { ESearchType, EUserType, ERoleType } from '@shared/enums'
import { IQueryParams } from '@shared/interfaces/queryParams'
import { User } from '@shared/interfaces/user'
import { IAamsItem, IAamsLevel } from '@shared/components/aams-select/aams-select.interface'
import { setupUserRoleTypeSelectionPermit, omitEmptyValue } from 'app/utils/helpers'

export class ListBaseClass<T, Q> {
  public defaultSize = 10
  public defaultPi = 1

  public get size() {
    return this._size
  }

  public set size(size) {
    this._size = size
    this.ps = this.size
    this.defaultParams.size = this.size
  }

  constructor(
    public msg: NzMessageService,
    public i18n: I18NService,
    public route: ActivatedRoute,
    public router: Router,
    public settings?: SettingsService, // Optional constructor item
  ) {
    Object.assign(this.params , this.defaultParams)
    // Get the current login user
    if (settings) {
      this.authUser = settings.user as User
    }
  }

  private _size = this.defaultSize

  /* 页码 */
  public pi = this.defaultPi

  /* 显示搜索项目 */
  public ifShowSearch = false

  /*  每页展示的数据量*/
  public ps: number = this.size

  /* 总数，显示在底部 */
  public total: number

  /* 加载状态 */
  public loading: boolean

  /* 默认搜索条件 */
  public defaultParams: IQueryParams = {
    size: 10,
    page: 1,
  }

  /* 向后台 query 的参数 */
  public params: IQueryParams = {
    size: this.size,
    page: this.pi,
  }

  /* 底部导航栏配置 */
  public stPage: STPage = {
    front: false,
    total: `${this.trans('GENERAL.ST.TOTAL')}`,
    showQuickJumper: true,
  }

  /* 表格宽度模式 */
  widthMode: STWidthMode = {
    type: 'strict',
    strictBehavior: 'truncate',
  }

  /* 滚动属性 */
  scroll = {
    x: '300px'
  }

  /* 表格组件 */
  @ViewChild('st') st: STComponent

  /* 渲染的数据源 */
  public data: T[]

  /* 搜索数据规则 */
  public searchSchema: ISearchSchema[]
  public customSchema: ISearchSchema[]

  public authUser: User = null

  searchValid = true
  // 搜索传值机构项
  selectedAcquirer: IAamsItem
  // 搜索传值集团项
  selectedGroup: IAamsItem
  // 搜索传值商户项
  selectedMerchant: IAamsItem
  // 搜索传值门店项
  selectedStore: IAamsItem

  isAgentUser: boolean
  isAcquirerUser: boolean
  isMerchantUser: boolean
  isStoreUser: boolean

  /**
   * Both below are the same but backend handle 2 different set of
   * frontend must be 2 set of well as they submit different type of variable to backend
   */
  userTypes = [
    { label: this.trans('USERTYPE.INSTITUTION'), value: EUserType.Institution, key: EUserType.Institution },
    { label: this.trans('USERTYPE.AGENT'), value: EUserType.Agent, key: EUserType.Agent  },
    { label: this.trans('USERTYPE.MERCHANT'), value: EUserType.Merchant, key: EUserType.Merchant },
    { label: this.trans('USERTYPE.STORE'), value: EUserType.Store, key: EUserType.Store },
  ]
  userRoles = [
    { label: this.trans('ROLE_LEVEL.INSTITUTION'), value: ERoleType.Acquirer, key: EUserType.Institution },
    { label: this.trans('ROLE_LEVEL.AGENT'), value: ERoleType.Group, key: EUserType.Agent },
    { label: this.trans('ROLE_LEVEL.MERCHANT'), value: ERoleType.Merchant, key: EUserType.Merchant },
    { label: this.trans('ROLE_LEVEL.STORE'), value: ERoleType.Store, key: EUserType.Store },
  ]
  /** Remove this once backend combine this to one set. */

  /** STChange 中的 type 没有定义 change, 但实际却有 change */
  onChange(event: any) {
    const { pi, type } = event
    if (type === 'click' || type === 'dblClick' || type === 'change') {
      return
    }
    this.params.page = pi
    this.queryTableDataWithParams()
    this.pi = pi
  }
  queryTableDataWithParams() {/* 子类去实现 */}

  parseParams(): Observable<any> {
    return of(this.route.queryParams.subscribe((queryParams: IQueryParams) => {
      if (this.hasQueryParams(queryParams)) {
        Object.assign(this.params, queryParams)
        this.fullfilSearchSchema(queryParams)
        this.showSearchBlock()
      }
    }))
  }

  handleSearch() {
    if (!this.checkSearchParamsValidation()) {
      return
    }
    this.resetParams()
    this.formatSearchParams()
    this.writeParamsIntoUrl()
    this.queryTableDataWithParams()
    this.resetPageNum()
  }

  resetParams() {
    this.params = {
      size: this.size,
      page: this.pi,
    }
    Object.assign(this.params, this.defaultParams)
  }

  resetPageNum() {
    this.pi = 1
  }

  setDefaultParams(defaultParams) {
    Object.assign(this.defaultParams, defaultParams)
    this.resetParams()
  }

  formatSearchParams(): void {
    const queryObj = {}
    if (this.searchSchema) {
      this.searchSchema.forEach(data => {
        if (!data) return
        /* 如果存在 customValue, customValue 是个函数 */
        if (data.customFunc && data.index) {
          /* 如果存在多个值, 即 index 的值是数组 */
          if (data.index instanceof Array) {
            queryObj[data.index[0]] = data.customFunc.call(this)[0]
            queryObj[data.index[1]] = data.customFunc.call(this)[1]
          } else {
            /* 单个值 */
            queryObj[data.index as string] = data.customFunc.call(this)
          }
          return
        }

        if (data.value instanceof Date) {
          queryObj[data.index as string] = dayjs(data.value).format('YYYY-MM-DDTHH:mm:ssZ')
          return
        }

        if (data.type === ESearchType.dateRange) {
          const range = data.value as Array<any>
          if (range.length !== 2) {
            /* 选择日期为空，则返回  */
            return
          }
          /* 如果index是Array的话里面都会是key */
          if (data.index instanceof Array) {
            queryObj[data.index[0]] = dayjs(range[0]).hour(0).minute(0).second(0).format('YYYY-MM-DDTHH:mm:ssZ')
            queryObj[data.index[1]] = dayjs(range[1]).add(1, 'day').hour(0).minute(0).second(0).format('YYYY-MM-DDTHH:mm:ssZ')
          } else {
            /* ng-zorro 返回的时间默认是当前的时间，这里将其处理成00:00 ~ 23:59 */
            queryObj['createdAtFrom'] = dayjs(range[0]).hour(0).minute(0).second(0).format('YYYY-MM-DDTHH:mm:ssZ')
            queryObj['createdAtTo'] = dayjs(range[1]).add(1, 'day').hour(0).minute(0).second(0).format('YYYY-MM-DDTHH:mm:ssZ')
          }
          return
        }
        if (data.value) {
          queryObj[data.index as string] = data.value
        }
      })
    }
    Object.assign(this.params, queryObj)
  }

  /**
   * 该用组件复用的方式，不再需要手动填充
   */
  writeParamsIntoUrl(): void {
    const shortedParams = omitEmptyValue(this.params)
    delete shortedParams.page
    delete shortedParams.size
    this.router.navigate([], {
      relativeTo: this.route,
      queryParams: shortedParams,
      replaceUrl: true,
    })
  }

  /**
   * 该用组件复用的方式，不再需要手动填充
   */
  fullfilSearchSchema(queryParams: any): void {
    const keys = Object.keys(queryParams)
    this.searchSchema.forEach(schema => {
      if (keys.includes(schema.index as string)) {
        schema.value = queryParams[schema.index as string]
      }
    })
    if (queryParams.startTime && queryParams.endTime) {
      this.searchSchema[0].value = [queryParams.startTime, queryParams.endTime]
    }
  }

  hasQueryParams(queryParams): boolean {
    return Object.keys(queryParams).length > 0
  }

  showSearchBlock(): void {
    this.ifShowSearch = true
  }

  trans(str: string, interpolateParams?: Object): string {
    return this.i18n.fanyi(str, interpolateParams)
  }

  renderList(res): void {
    const { data, total } = res
    this.data = data
    this.total = total
    this.loading = false
  }

  getListError(err): void {
    this.loading = false
    console.error('getListError is', err)
  }

  checkSearchParamsValidation(): boolean {
    return this.searchValid
  }

  initAcquirer(): void {
    const disabled = (
      this.authUser.userType === EUserType.Agent ||
      this.authUser.userType === EUserType.Merchant ||
      this.authUser.userType === EUserType.Store  ||
      this.authUser.userType === EUserType.Institution
    )
    if (disabled) {
      this.selectedAcquirer = {
        code: this.authUser.insCode,
        name: this.authUser.insName,
        nameEN: this.authUser.insNameEN
      }
    }
    this.isAcquirerUser = disabled
  }

  initAgent(): void {
    const disabled = (
      this.authUser.userType === EUserType.Agent ||
      this.authUser.userType === EUserType.Merchant ||
      this.authUser.userType === EUserType.Store
    )
    if (disabled) {
      this.selectedGroup = {
        code: this.authUser.agentCode,
        name: this.authUser.agentName,
        nameEN: this.authUser.agentNameEN
      }
    }
    this.isAgentUser = disabled
  }

  initMerchant(): any {
    const disabled = (
      this.authUser.userType === EUserType.Merchant ||
      this.authUser.userType === EUserType.Store
    )
    if (disabled) {
      this.selectedMerchant = {
        code: this.authUser.intMerCode,
        name: this.authUser.merName,
        nameEN: this.authUser.merNameEN
      }
    }
    this.isMerchantUser = disabled
  }

  initStore(): any {
    const disabled = (
      this.authUser.userType === EUserType.Store
    )
    if (disabled) {
      this.selectedStore = {
        code: this.authUser.intStoreCode,
        name: this.authUser.storeName,
        nameEN: this.authUser.storeNameEN
      }
    }
    this.isStoreUser = disabled
  }

  setupUserTypeSelectionPermit() {
    // Get current login user role
    const authUser = this.settings.user as User
    this.userTypes = setupUserRoleTypeSelectionPermit(authUser, this.userTypes)
  }

  setupUserRoleSelectionPermit() {
    // Get current login user role
    const authUser = this.settings.user as User
    this.userTypes = setupUserRoleTypeSelectionPermit(authUser, this.userRoles)
  }

  aamsChangeHandler(data: {level: IAamsLevel, value: IAamsItem}): void {
    const { level, value } = data
    switch (level) {
      case IAamsLevel.Acquirer:
        this.clearSelectedGroup()
        this.clearSelectedMerchant()
        this.clearSelectedStore()
        break
      case IAamsLevel.Group:
        this.clearSelectedMerchant()
        this.clearSelectedStore()
        break
      case IAamsLevel.Merchant:
        this.clearSelectedStore()
        break
      case IAamsLevel.Group:
        break
    }
  }

  clearSelectedGroup(): void {
    this.selectedGroup = null
  }

  clearSelectedMerchant(): void {
    this.selectedMerchant = null
  }

  clearSelectedStore(): void {
    this.selectedStore = null
  }
}
